﻿namespace Pyramid.Text
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Pyramid.Utility;

    public class Tcc4Encoder
        : Encoder
    {
        private static char[] characterMap = ";abcdefghijklmnopqrstuvwxyz !\"\\?%&'()*+,0123456789-./:@ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();

        private static sbyte[] characterCodeMap;

        static Tcc4Encoder()
        {
            characterCodeMap = new sbyte[characterMap.Select(x => (int)x).Max() + 1];

            for (int characterIndex = 0; characterIndex < characterMap.Length; characterIndex++)
            {
                characterCodeMap[(int)characterMap[characterIndex]] = (sbyte)(characterIndex + CHARACTER_CODE_OFFSET);
            }
        }

        private const int CHARACTER_CODE_OFFSET = -40;

        private const int CHARACTERS_PER_TRYTE = 2;

        private List<char> characterBuffer = new List<char>();

        public override int GetTryteCount(char[] chars, int index, int count, bool flush)
        {
            Validate.IsLongEnough(chars, index + count, "chars");

            int characterCount = count + characterBuffer.Count;

            int tryteCount = characterCount / CHARACTERS_PER_TRYTE;

            if (flush && ((characterCount % CHARACTERS_PER_TRYTE) > 0))
            {
                tryteCount++;
            }

            return tryteCount;
        }

        public override int GetTrytes(char[] chars, int charIndex, int charCount, Tryte[] trytes, int tryteIndex, bool flush)
        {
            int trytecount = GetTryteCount(chars, charIndex, charCount, flush);

            Validate.IsNotNull(trytes, "trytes");

            Validate.IsLongEnough(trytes, tryteIndex + trytecount, "trytes");

            var temporaryBuffer = new List<char>(characterBuffer);

            for (; charIndex < charCount; charIndex++)
            {
                char currentCharacter = chars[charIndex];

                CheckCharacterIsValid(currentCharacter);

                temporaryBuffer.Add(currentCharacter);

                if (temporaryBuffer.Count >= CHARACTERS_PER_TRYTE)
                {
                    TransferTryte(temporaryBuffer, CHARACTERS_PER_TRYTE, trytes, tryteIndex);

                    tryteIndex++;
                }
            }

            if (flush && temporaryBuffer.Count > 0)
            {
                TransferTryte(temporaryBuffer, temporaryBuffer.Count, trytes, tryteIndex);
            }

            characterBuffer = temporaryBuffer;

            return trytecount;
        }

        private void TransferTryte(List<char> characterBuffer, int characterCount, Tryte[] trytes, int tryteIndex)
        {
            trytes[tryteIndex] = GetTryte(characterBuffer, characterCount);

            characterBuffer.Clear();
        }

        private static Tryte GetTryte(List<char> characterBuffer, int count)
        {
            Tryte result = 0;

            for (int characterIndex = 0; characterIndex < count; characterIndex++)
            {
                result += (Tryte)(GetCharacterCode(characterBuffer[characterIndex]) * Math.Pow(81, characterIndex));
            }

            return result;
        }

        private static Tryte GetCharacterCode(char character)
        {
            return characterCodeMap[(int)character];
        }

        private void CheckCharacterIsValid(char currentCharacter)
        {
            if (!characterMap.Contains(currentCharacter))
            {
                // TODO Replace this with the proper Encoding Fallback mechanism
                throw new Exception("Character not in TCC4 character set.");
            }
        }

        public override void Reset()
        {
            this.characterBuffer.Clear();

            base.Reset();
        }
    }
}
